/*-------------------------------------------------------------------------
 *
 * buf_internals.h
 *	  缓冲区管理器及其缓冲区替换策略的内部定义。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/storage/buf_internals.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef BUFMGR_INTERNALS_H
#define BUFMGR_INTERNALS_H

#include "port/atomics.h"
#include "storage/buf.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
#include "storage/latch.h"
#include "storage/lwlock.h"
#include "storage/shmem.h"
#include "storage/smgr.h"
#include "storage/spin.h"
#include "utils/relcache.h"

/*
 * 缓冲区状态是一个32位变量，其中结合以下数据。
 *
 * - 18位引用计数
 * - 4位使用计数
 * - 10位标志
 *
 * 组合这些值可以在不锁定
 * 缓冲区头部的情况下执行某些操作，
 * 通过与CAS循环一起修改它们。
 *
 * 缓冲区状态组件的定义如下。
 */
#define BUF_REFCOUNT_ONE 1
#define BUF_REFCOUNT_MASK ((1U << 18) - 1)
#define BUF_USAGECOUNT_MASK 0x003C0000U
#define BUF_USAGECOUNT_ONE (1U << 18)
#define BUF_USAGECOUNT_SHIFT 18
#define BUF_FLAG_MASK 0xFFC00000U

/* 从缓冲区状态获取引用计数和使用计数 */
#define BUF_STATE_GET_REFCOUNT(state) ((state) & BUF_REFCOUNT_MASK)
#define BUF_STATE_GET_USAGECOUNT(state) (((state) & BUF_USAGECOUNT_MASK) >> BUF_USAGECOUNT_SHIFT)

/*
 * 缓存描述符的标志
 *
 * 注意：BM_TAG_VALID 本质上意味着有一个缓冲区哈希表
 * 条目与缓冲区的标签相关联。
 */
#define BM_LOCKED				(1U << 22)	/* 缓冲区头部被锁定 */
#define BM_DIRTY				(1U << 23)	/* 需要写入数据 */
#define BM_VALID				(1U << 24)	/* 数据是有效的 */
#define BM_TAG_VALID			(1U << 25)	/* 标签已分配 */
#define BM_IO_IN_PROGRESS		(1U << 26)	/* 读取或写入正在进行中 */
#define BM_IO_ERROR				(1U << 27)	/* 之前的 I/O 失败 */
#define BM_JUST_DIRTIED			(1U << 28)	/* 自写入开始以来已被修改 */
#define BM_PIN_COUNT_WAITER		(1U << 29)	/* 有等待者等待独占锁 */
#define BM_CHECKPOINT_NEEDED	(1U << 30)	/* 必须为检查点写入 */
#define BM_PERMANENT			(1U << 31)	/* 永久缓冲区（未记录，不是初始化分叉） */
/*
 * usage_count 允许的最大值在精确度和时钟扫描缓冲区管理算法的速度之间
 * 存在折中。一个较大的值（与 NBuffers 相当）将近似于 LRU 语义。
 * 但是，找到一个空闲缓冲区可能需要多达 BM_MAX_USAGE_COUNT+1 次完整的
 * 时钟扫描，因此在实践中我们不希望这个值过大。
 */
#define BM_MAX_USAGE_COUNT	5

/*
 * 缓冲区标签标识缓冲区包含的磁盘块。
 *
 * 注意：BufferTag 数据必须足够确定在哪里写入该块，不能参考
 * pg_class 或 pg_tablespace 条目。后端刷新缓冲区时可能甚至认为
 * 关系尚不可见（其事务可能在创建关系的事务之前就已开始）。
 * 存储管理器必须能处理这一点。
 *
 * 注意：如果结构中有任何填充字节，INIT_BUFFERTAG 将必须被修复以将其置为零，
 * 因为该结构用作哈希键。
 */
typedef struct buftag
{
	RelFileNode rnode;			/* 物理关系标识符 */
	ForkNumber	forkNum;
	BlockNumber blockNum;		/* blknum 相对于关系的开始 */
} BufferTag;

#define CLEAR_BUFFERTAG(a) \
( \
	(a).rnode.spcNode = InvalidOid, \
	(a).rnode.dbNode = InvalidOid, \
	(a).rnode.relNode = InvalidOid, \
	(a).forkNum = InvalidForkNumber, \
	(a).blockNum = InvalidBlockNumber \
)

#define INIT_BUFFERTAG(a,xx_rnode,xx_forkNum,xx_blockNum) \
( \
	(a).rnode = (xx_rnode), \
	(a).forkNum = (xx_forkNum), \
	(a).blockNum = (xx_blockNum) \
)

#define BUFFERTAGS_EQUAL(a,b) \
( \
	RelFileNodeEquals((a).rnode, (b).rnode) && \
	(a).blockNum == (b).blockNum && \
	(a).forkNum == (b).forkNum \
)

/*
 * 共享缓冲区映射表被分区以减少争用。
 * 要确定给定标签需要哪个分区锁，请使用 BufTableHashCode() 计算
 * 标签的哈希代码，然后应用 BufMappingPartitionLock()。
 * 注意：NUM_BUFFER_PARTITIONS 必须是 2 的幂!
 */
#define BufTableHashPartition(hashcode) \
	((hashcode) % NUM_BUFFER_PARTITIONS)
#define BufMappingPartitionLock(hashcode) \
	(&MainLWLockArray[BUFFER_MAPPING_LWLOCK_OFFSET + \
		BufTableHashPartition(hashcode)].lock)
#define BufMappingPartitionLockByIndex(i) \
	(&MainLWLockArray[BUFFER_MAPPING_LWLOCK_OFFSET + (i)].lock)

/*
 *	BufferDesc -- 单个共享缓冲区的共享描述符/状态数据。
 *
 * 注意：在检查或更改标签、状态或 wait_backend_pgprocno 字段时必须
 * 持有缓冲区头部锁（BM_LOCKED 标志）。一般来说，缓冲区头部锁
 * 是自旋锁，与标志、引用计数和使用计数组合为单个原子变量。
 * 这种布局允许我们在不实际获取和释放自旋锁的情况下，以单个
 * 原子操作执行某些操作；例如，增加或减少引用计数。buf_id 字段在初始化后
 * 永远不会更改，因此不需要锁定。freeNext 由 buffer_strategy_lock 保护，而不是
 * 缓冲区头部锁。LWLock 可以自行处理。缓冲区头部锁 *不* 用于控制对
 * 缓冲区中数据的访问！
 *
 * 假设在持有缓冲区头部锁时没有人更改状态字段。因此，缓冲区头部
 * 锁的持有者可以在释放锁的同时以单次写入进行复杂的状态变量更新
 * （清除 BM_LOCKED 标志）。另一方面，在未持有缓冲区头部锁的情况下
 * 更新状态仅限于 CAS，确保 BM_LOCKED 标志未设置。不允许进行原子自增/自减、
 * 逻辑或与等操作。
 *
 * 一个例外是如果我们锁定了缓冲区，则其标签不能在我们底下更改，
 * 因此我们可以在不锁定缓冲区头部的情况下检查标签。此外，在某些地方
 * 我们在不锁定缓冲区头部的情况下进行一次性读取标志；这通常是在
 * 我们不期望被测试的标志位变化的情况下。
 *
 * 如果另一个后端将缓冲区锁定，我们无法从磁盘页面中物理移除项。
 * 因此，一个后端可能需要等待所有其他锁定解除。这通过将自己的 pgprocno
 * 存储到 wait_backend_pgprocno 中并设置标志位 BM_PIN_COUNT_WAITER 来发出信号。
 * 目前，每个缓冲区只能有一个这样的等待者。
 *
 * 我们对本地缓冲区头使用同一结构，但不使用锁，并且并非所有标志位也有用。
 * 为了避免不必要的开销，对状态字段的操作应在没有实际原子操作的情况下进行
 * （即仅使用 pg_atomic_read_u32() 和 pg_atomic_unlocked_write_u32()）。
 *
 * 在添加或重新排序成员时要小心避免增加结构的大小。保持其低于 64 字节
 * （最常见的 CPU 缓存行大小）对性能相当重要。
 *
 * 每个缓冲区的 I/O 条件变量当前保存在此结构外部一个单独的数组中。
 * 它们可以移到这里，并且仍然适合在常见系统中该限制之内，但目前尚未完成。
 */
typedef struct BufferDesc
{
	BufferTag	tag;			/* 包含在缓冲区中的页面的 ID */
	int			buf_id;			/* 缓冲区的索引号（从 0 开始） */

	/* 标签的状态，包含标志、引用计数和使用计数 */
	pg_atomic_uint32 state;

	int			wait_backend_pgprocno;	/* 引用计数等待者的后端 */
	int			freeNext;		/* 在空闲链中的链接 */
	LWLock		content_lock;	/* 锁定对缓冲区内容的访问 */
} BufferDesc;

/*
 * 对缓冲区头的并发访问被证明在缓存行对齐时更高效。
 * 因此，我们强制 BufferDescriptors 数组的开头位于
 * 缓存行边界上，并强制元素为缓存行大小。
 *
 * XXX：由于这主要是在高度并发的工作负载中
 * 相关，这些工作负载现在可能都是 64 位，并且在
 * 32 位系统上浪费空间可能会更加明显，因此我们不
 * 强制步幅为缓存行大小。如果有人进行实际的性能
 * 测试，我们可以重新评估。
 *
 * 注意，本地缓冲区描述符不强制对齐 - 由于没有
 * 并发访问，因此不太可能有益。
 *
 * 我们在这里使用 64 字节的缓存行大小，因为这是最
 * 常见的大小。增大这个大小将浪费内存。即使在
 * 在 32 或 128 字节行大小的平台上运行，保持对齐
 * 边界并避免虚假共享仍然是好的。
 */
#define BUFFERDESC_PAD_TO_SIZE	(SIZEOF_VOID_P == 8 ? 64 : 1)

typedef union BufferDescPadded
{
	BufferDesc	bufferdesc;
	char		pad[BUFFERDESC_PAD_TO_SIZE];
} BufferDescPadded;

#define GetBufferDescriptor(id) (&BufferDescriptors[(id)].bufferdesc)
#define GetLocalBufferDescriptor(id) (&LocalBufferDescriptors[(id)])

#define BufferDescriptorGetBuffer(bdesc) ((bdesc)->buf_id + 1)

#define BufferDescriptorGetIOCV(bdesc) \
	(&(BufferIOCVArray[(bdesc)->buf_id]).cv)
#define BufferDescriptorGetContentLock(bdesc) \
	((LWLock*) (&(bdesc)->content_lock))

extern PGDLLIMPORT ConditionVariableMinimallyPadded *BufferIOCVArray;

/*
 * freeNext 字段要么是下一个空闲列表条目的索引，
 * 要么是这些特殊值之一：
 */
#define FREENEXT_END_OF_LIST	(-1)
#define FREENEXT_NOT_IN_LIST	(-2)

/*
 * 用于获取/释放共享缓冲区头的自旋锁的函数。请
 * 不要将这些应用于本地缓冲区！
 */
extern uint32 LockBufHdr(BufferDesc *desc);
#define UnlockBufHdr(desc, s)	\
	do {	\
		pg_write_barrier(); \
		pg_atomic_write_u32(&(desc)->state, (s) & (~BM_LOCKED)); \
	} while (0)


/*
 * PendingWriteback 和 WritebackContext 结构用于
 * 保持有关待发往操作系统的刷新请求的信息。
 */
typedef struct PendingWriteback
{
	/* 可在此存储不同类型的待处理刷新 */
	BufferTag	tag;
} PendingWriteback;

/* 在 bufmgr.h 中前置声明的结构 */
typedef struct WritebackContext
{
	/* 指向最大合并写回请求数量的指针 */
	int		   *max_pending;

	/* 当前待写回请求的数量 */
	int			nr_pending;

	/* 待处理请求 */
	PendingWriteback pending_writebacks[WRITEBACK_MAX_PENDING_FLUSHES];
} WritebackContext;

/* 在 buf_init.c 中 */
extern PGDLLIMPORT BufferDescPadded *BufferDescriptors;
extern PGDLLIMPORT WritebackContext BackendWritebackContext;

/* 在 localbuf.c 中 */
extern PGDLLIMPORT BufferDesc *LocalBufferDescriptors;

/* 在 bufmgr.c 中 */

/*
 * 用于在检查点按文件排序缓冲区的结构。
 *
 * 该结构在共享内存中为每个缓冲区分配，因此应该尽可能小。
 */
typedef struct CkptSortItem
{
	Oid			tsId;
	Oid			relNode;
	ForkNumber	forkNum;
	BlockNumber blockNum;
	int			buf_id;
} CkptSortItem;

extern PGDLLIMPORT CkptSortItem *CkptBufferIds;

/*
 * 内部缓冲区管理例程
 */
/* bufmgr.c */
extern void WritebackContextInit(WritebackContext *context, int *max_pending);
extern void IssuePendingWritebacks(WritebackContext *context);
extern void ScheduleBufferTagForWriteback(WritebackContext *context, BufferTag *tag);

/* freelist.c */
extern BufferDesc *StrategyGetBuffer(BufferAccessStrategy strategy,
									 uint32 *buf_state);
extern void StrategyFreeBuffer(BufferDesc *buf);
extern bool StrategyRejectBuffer(BufferAccessStrategy strategy,
								 BufferDesc *buf);

extern int	StrategySyncStart(uint32 *complete_passes, uint32 *num_buf_alloc);
extern void StrategyNotifyBgWriter(int bgwprocno);

extern Size StrategyShmemSize(void);
extern void StrategyInitialize(bool init);
extern bool have_free_buffer(void);

/* buf_table.c */
extern Size BufTableShmemSize(int size);
extern void InitBufTable(int size);
extern uint32 BufTableHashCode(BufferTag *tagPtr);
extern int	BufTableLookup(BufferTag *tagPtr, uint32 hashcode);
extern int	BufTableInsert(BufferTag *tagPtr, uint32 hashcode, int buf_id);
extern void BufTableDelete(BufferTag *tagPtr, uint32 hashcode);

/* localbuf.c */
extern PrefetchBufferResult PrefetchLocalBuffer(SMgrRelation smgr,
												ForkNumber forkNum,
												BlockNumber blockNum);
extern BufferDesc *LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum,
									BlockNumber blockNum, bool *foundPtr);
extern void MarkLocalBufferDirty(Buffer buffer);
extern void DropRelFileNodeLocalBuffers(RelFileNode rnode, ForkNumber forkNum,
										BlockNumber firstDelBlock);
extern void DropRelFileNodeAllLocalBuffers(RelFileNode rnode);
extern void AtEOXact_LocalBuffers(bool isCommit);

#endif							/* BUFMGR_INTERNALS_H */
